/*
 * author: VDaras
 */

#include <map>
#include <utility>
#include <sstream>

#include "KeyMapper.h"
#include "Logger.h"
#include "parsers/tinyxml.h"
#include "GlobalTimer.h"
#include "Globals.h"

KeyMapper::KeyMapper()
{
    ReadFile();
}

KeyMapper::~KeyMapper()
{
}

/**
 * Creates a map associating sdlk values with a literal
 */
void KeyMapper::CreateAsciiMap()
{

    //pass all letters
    for (int i = SDLK_a; i <= SDLK_z; i++)
    {
        std::ostringstream oss;
        oss << (char) toupper(i);
        m_enumMap[oss.str()] = (SDLKey) i;
    }

    //pass arrows
    m_enumMap["UpArrow"] = SDLK_UP;
    m_enumMap["DownArrow"] = SDLK_DOWN;
    m_enumMap["LeftArrow"] = SDLK_LEFT;
    m_enumMap["RightArrow"] = SDLK_RIGHT;

    //pass all Fx buttons
    int f = 1;
    for (int i = SDLK_F1; i <= SDLK_F12; i++, f++)
    {
        std::ostringstream oss;
        oss << "F" << f;
        m_enumMap[oss.str()] = (SDLKey) i;
    }

    //special buttons
    m_enumMap["Tab"] = SDLK_TAB;
    m_enumMap["Space"] = SDLK_SPACE;
    m_enumMap["Backspace"] = SDLK_BACKSPACE;
    m_enumMap["Enter"] = SDLK_RETURN;
    m_enumMap["Escape"] = SDLK_ESCAPE;
    //navigation buttons
    m_enumMap["Insert"] = SDLK_INSERT;
    m_enumMap["Home"] = SDLK_HOME;
    m_enumMap["End"] = SDLK_END;
    m_enumMap["Delete"] = SDLK_DELETE;
    m_enumMap["PageDown"] = SDLK_PAGEDOWN;
    m_enumMap["PageUp"] = SDLK_PAGEUP;

    //pass modifiers ( left and right )
    m_enumMap["AltL"] = SDLK_LALT;
    m_enumMap["CtrlL"] = SDLK_LCTRL;
    m_enumMap["ShiftL"] = SDLK_LSHIFT;

    m_enumMap["AltR"] = SDLK_RALT;
    m_enumMap["CtrlR"] = SDLK_RCTRL;
    m_enumMap["ShiftR"] = SDLK_RSHIFT;

}

/**
 * Creates a map associating sdlmod values with a literal
 */
void KeyMapper::CreateModMap()
{
    m_modMap["NONE"] = KMOD_NONE;
    m_modMap["AltL"] = KMOD_LALT;
    m_modMap["CtrlL"] = KMOD_LCTRL;
    m_modMap["ShiftL"] = KMOD_LSHIFT;

    m_modMap["AltR"] = KMOD_RALT;
    m_modMap["CtrlR"] = KMOD_RCTRL;
    m_modMap["ShiftR"] = KMOD_RSHIFT;
    //add any mod tha is needed
    validMods = (SDLMod) (KMOD_LALT | KMOD_LCTRL | KMOD_LSHIFT | KMOD_RALT | KMOD_RCTRL | KMOD_RSHIFT);
}

/**
 * Reads key map file
 */
bool KeyMapper::ReadFile(char const * group /*= NULL*/)
{
    CreateAsciiMap();
    CreateModMap();

    //Load file
    TiXML::TiXmlDocument xFile(Globals::GetInstance()->GetFile("Keymap").c_str());
    if (!xFile.LoadFile())
    {
        LOG(Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR) << "Failed to load keymap.xml";
        return false;
    }

    //get the root of the file : map
    TiXML::TiXmlElement* xMap = xFile.FirstChildElement("keymap");
    if (!xMap)
    {
        LOG(Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR) << "Failed to retrieve root of keymap.xml";
        return false;
    }
    
    TiXML::TiXmlElement* xGroup;
    if(group == NULL)
    {
        xGroup = xMap->FirstChildElement(xMap->Attribute("default"));
    }
    else
    {
        xGroup = xMap->FirstChildElement(group);
    }
    
    TiXML::TiXmlElement* xMapNode = xGroup->FirstChildElement();
    while (xMapNode)
    {
        std::string key = xMapNode->Attribute("key");
        std::string value = xMapNode->Attribute("value");
        const char* mod = xMapNode->Attribute("mod");

        //check if key is valid key name
        if (m_enumMap.find(key) == m_enumMap.end())
        {
            LOG(Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR) << "Invalid key was specified in  keymap.xml (" << key << ")";
            return false;
        }

        if (!mod)
            AssociateKey(value, m_enumMap[key]);
        else
        {
            //check if mod is valid mod name
            if (m_modMap.find(mod) == m_modMap.end())
            {
                LOG(Logger::CHANNEL_LOADING, LogFileStream::LEVEL_ERROR) << "Invalid mod was specified in  keymap.xml (" << mod << ")";
                return false;
            }
            AssociateKey(value, m_enumMap[key], m_modMap[mod]);
        }

        TiXML::TiXmlElement* xTmp = xMapNode->NextSiblingElement();
        xMapNode->Clear();
        xMapNode = xTmp;
    }

    xGroup->Clear();
    xMap->Clear();
    xFile.Clear();

    return true;
}

/**
 * Associates an SDLKey with a string. E.g associate SDLK_SPACE
 * with "JUMP".
 *
 * @param association - the string to associate the key with
 * @param key - the key to associate (part 1 of the key)
 * @param mod - the mod to associate (part 2 of the key)
 */
void KeyMapper::AssociateKey(const std::string &association, SDLKey key, SDLMod mod/*= NONE*/)
{
    //create key
    InputKey k;
    k.key = key;
    k.mod = mod;

    //search if the key already has an association
    AssociationMap::iterator result(m_keyAssociations.find(k));

    //if it has
    if (result != m_keyAssociations.end())
    {
        //change the association's string
        result->second = association;
    }
    else
    {
        //create a new association
        m_keyAssociations.insert(std::make_pair(k, association));
    }
}

/**
 * Returns the string that the key SDLkey is mapped to.
 *
 * @param key
 * @param retval - OUTPUT parameter, stores the associated string in here.
 * @return a boolean value indicating if the key SDLKey is mapped to a value
 */

bool KeyMapper::MapKey(std::string &retval, SDLKey key, SDLMod mod/*= NONE*/) const
{
    //create key
    InputKey k;
    k.key = key;
    k.mod = (SDLMod) (mod & validMods);

    //search for the key
    AssociationMap::const_iterator result(m_keyAssociations.find(k));
    bool ret = false;

    if ((ret = result != m_keyAssociations.end()))
    {
        retval = result->second;
    }
    else
    {//if key mod combination was not found try key alone
        //create key
        InputKey k;
        k.key = key;
        k.mod = KMOD_NONE;

        //search for the key
        AssociationMap::const_iterator result(m_keyAssociations.find(k));
        if ((ret = result != m_keyAssociations.end()))
        {
            retval = result->second;
        }
        else
        {
            retval = "";
        }
    }

    return ret;
}

SDLMod KeyMapper::GetValidMods() const
{
    return validMods;
}
